/**
 * Milestone version,Backup
 * Mobile Support Only,don't use it in IE
 * Created by jale on 14-4-22.
 */
(function () {
    var debug = false;
    var I = {
        i: function (info) {
            debug && console.info('[Info]' + info);
        },
        e: function (info) {
            debug && console.error('[Error]' + info);
        }
    };


    var D = document, W = window, $, $c = {},$j = '__jamObject';
    var $pf = parseFloat, $hp = 'hasOwnProperty';
    /**
     * Init as jQuery method
     */
    var jam = $ = W.jam = W.$ = function (expr) {
        if (expr.jamObjects) {
            return expr;
        }
        var t = typeof expr,$jam;
        if (t == 'function') {
            I.i('[jam init]callback..');
            return $(D).ready(expr);
        }else if(t == 'string') {
            $jam = $c[expr];
        }else if(t == 'object') {
            $jam = expr[$j];
        }
        return $jam || new jam.fn.init(expr) ;
    };
    jam.fn = jam.prototype = {
        constructor: jam,
        init: function (expr) {
            I.i('[jam init]New..');
            var $i = this,t = typeof expr;
            var sels = t == 'string' ? D.querySelectorAll(expr) : expr.length ? expr : [expr];
            this.jamObjects = sels;
            for (var i = 0; i < $i.jamObjects.length; i++) {
                $i[i] = $i.jamObjects[i];
            }
            this.length = sels.length;
            this.get = function (index) {
                return $($i[index]);
            };
            this.eq = function (index) {
                return $i.jamObjects[index];
            };
            if(sels.length == 1) {
                sels[0][$j] = $i;
                t == 'string' && ($c[expr] = $i);
            }
            return $i;
        }
    };

    jam.fn.init.prototype = jam.fn;

    /**
     * data storage support
     * @param k
     * @param v
     */
    $.fn.data = function (k,v) {
        if(v) return this.data[k];
        return this.each(function(){
            !$(this).data && ($(this).data = {});
            $(this).data[k] = v;
        });
    };

    /**
     * get & set value
     */
    $.fn.val = function(v) {
        var $i = this[0],t = $i.tagName;
        if(v) {
            return this.each(function(){
                switch (t) {
                    case 'SELECT' : {
                        this.find('option').each(function(){
                            var $i = this;
                            if($i.value == v) {
                                $i.selected = true;
                                return false;
                            }
                            return true;
                        });
                        break;
                    }
                    default : {
                        this.value = v;
                    }
                }
            });
        }
        switch (t) {
            case 'SELECT' : {
                var val = null;
                this.find('option').each(function(){
                    var $i = this;
                    if($i.selected) {
                        val = $i.value;
                        return false;
                    }
                    return true;
                });
                return val;
            }
            default : {
                return $i.value;
            }
        }
    };
    /**
     * search
     * not support expr ":selected"
     */
    $.fn.find = function (expr) {
        var hasId = true, objs;
        if (!this[0].id) {
            this[0].id = '_JAM_EXPR_ID_';
            hasId = false;
        }
        objs = $('#' + this[0].id + ' ' + expr);
        !hasId && (this[0].id = null);
        return objs;
    };

    /**
     * iterator
     * @param func
     * @returns {$.fn}
     */
    $.fn.each = function (func) {
        for (var i = 0; i < this.length; i++) {
            if (func.call(this[i]) === false) break;
        }
        return this;
    };

    /**
     * event support
     */
    var EventHandler = function (ev) {
        return function (func) {
            I.i('bind event ' + ev + ' to ' + this);
            return this.each(function () {
                this.addEventListener(ev, func, false);
            });
        }
    };

    function addEvent(events, names) {
        for (var i = 0; i < events.length; i++) {
            $.fn[names[i]] = new EventHandler(events[i]);
        }
    }

    addEvent(['touchstart', 'touchmove', 'touchend', 'submit', 'click', 'load', 'focus', 'blur', 'DOMContentLoaded'],
        ['touchStart', 'touchMove', 'touchEnd', 'submit', 'click', 'load', 'focus', 'blur', 'ready']);

    /**
     * css edit support,this method would not do anything about attribute unit,you must define it by yourself
     * @param css
     */
    $.fn.css = function (css) {
        return this.each(function () {
            for (var i in css) {
                if (css[$hp](i)) {
                    this.style[i] = css[i];
                }
            }
        });
    };

    /**
     * size and position support
     */
    $.fn.width = function (px) {
        if (px || px === 0) {
            var nu = $pnu(px);
            return this.each(function () {
                this.style.width = (nu.n + (nu.u || 'px'));
            });
        } else {
            return $pf(this[0].style.width || W.getComputedStyle(this[0])['width']);
        }
    };
    $.fn.height = function (px) {
        if (px || px === 0) {
            var nu = $pnu(px);
            return this.each(function () {
                this.style.height = (nu.n + (nu.u || 'px'));
            });
        } else {
            return  Math.max(this[0].clientHeight, this[0].scrollHeight);
        }
    };

    /**
     * animate support,this method is not support global chaining,
     * it has its own chain which like $(XX).animate().nextAnimate().nextAnimate()...start()...
     * ps:translate3d is not friendly enough to android
     * @param cfg
     * running state {
     *  -1 : not init,
     *  0 : stopped,
     *  1 : running
     *  2 : pausing
     *  3 : fast forward/backward
     * }
     *
     */
    var fps = 59, engine = null, frames = [];//
    $.fn.animate = function (cfg) {
        var self = this;
        var ctx = {
            o: self,
            ffb: 1,
            fp: 0,
            fn: Math.ceil(cfg.sec / (1000 / fps)),
            fa: {},
            fe: cfg.to,
            fs: cfg.from,
            fc: {},
            u: cfg.upgradeFunc || ufunc,
            c: cfg.callback,
            cf: null
        };
        return {
            ctx: ctx,
            init: function () {
                this.ctx.fp = 0;
                this.ctx.ffb = 1;
                this.ctx.fc = {};
                this.cf = null;
                this.fpp = null;
                this.r = 0;
                return this;
            },
            /**
             * start an animate group
             * @param s start all single-animate true | false
             * @returns {$.fn.animate}
             */
            start: function (s) {
                var $i = this;
                !engine && (engine = W.setInterval(function () {
                    for (var i = 0; i < frames.length; i++) {
                        frames[i].r == 1 && doAnimate(frames[i]);
                    }
                }, 1000 / fps));

                switch ($i.r) {
                    case -1 :
                    {
                        frames.push($i);
                    }
                    case 0:
                    {
                        $i.r = 1;
                        $i.ctx.o.css($i.ctx.fs).show();
                        break;
                    }
                    case 1: {
                        return this;
                    }
                    case 2 :
                    {
                        $i.r = $i.fpp[1];
                        $i.r == 1 && $i.ctx.o.css($i.fpp[0]).show();
                        !s && $i.n && $i.n.start();
                        break;
                    }
                }
                !s && $i.g && $i.g.start();
                return this;
            },
            stop: function () {
                I.i('animate stop!');
                this.n && this.n.start();
                this.ctx.c && this.ctx.c.call(this.ctx.o);
                this.init();
                return this;
            },
            pause: function (s) {
                if(this.r === 1) {
                    I.i('animate pause!');
                    this.fpp = [this.ctx.fc, this.r]; //save current css state and running state
                    this.r = 2;
                    !s && this.n && this.n.pause();
                    !s && this.g && this.g.pause();
                }
                return this;
            },
            isRunning: function () {
                return this.r === 1;
            },
            /**
             * forward & backward
             * @param n the frame NO.
             * @param s speed
             * @param func callback after every sub-animate reach n
             *
             * the type of s and n is number or array
             *
             */
            fb : function(n,s,func) {
                console.info('fb >>' + n + ',' + s);
                var c = this.ctx, fn = n.length ? n.shift() : n,cn = fn - c.fp;
                if(c.cf || !cn) return;
                this.start(true);
                c.cf = [fn,func,cn];
                c.ffb = (cn * 1000 / fps) / (s.length ? s.shift() : s);
                console.info('c.ffb >>' + c.ffb + ',' + cn);
                this.g && this.g.fb(n,s,func);
                this.n && this.n.fb(n,s,func);

            },
            r: -1, n: null, g: null,
            nextAnimate: nextAnimate  //the animate after current animate end
        }
    };

    function ufunc(r, ctx) {
        for (var i in ctx.fs) {
            if (ctx.fs[$hp](i) && ctx.fe[$hp](i)) {
                var s = $pf(ctx.fs[i]), e = $pf(ctx.fe[i]), nu = $pnu(ctx.fe[i]);
                ctx.fc[i] = (e - s) * r + s;
                nu.u && (ctx.fc[i] += nu.u);
            }
        }
        return ctx.fc;
    }

    function nextAnimate(expr, cfg, sync) {
        var ptr = this, it = sync ? 'g' : 'n';
        while (ptr[it] != null) ptr = ptr[it];
        ptr[it] = $(expr).animate(cfg);
        return this;
    }

    function doAnimate(atx) {
        var ctx = atx.ctx,bf = ctx.cf;
        if (atx.r !== 1) return;
        ctx.fp += ctx.ffb;
        ctx.fc = (ctx.fp >= ctx.fn ? ctx.fe : ctx.fn <= 0 ? ctx.fs : ctx.u(ctx.fp / ctx.fn, ctx));
        ctx.o.css(ctx.fc);
        if(bf && ((bf[2] > 0 && ctx.fp >= bf[0]) || (bf[2] < 0 && ctx.fp <= bf[0]))) {
            atx.pause(true);
            ctx.cf = null;
            ctx.fp = bf[0];
            ctx.ffb = 1;
            bf[1] && bf[1].call(atx);
        }
        (ctx.fp >= ctx.fn || ctx.fp <= 0) && atx.stop();

    }

    /**
     * visible support
     * @returns {$.fn}
     */
    $.fn.show = function () {
        return this.each(function () {
            this.style.display = '';
        });
    };
    $.fn.hide = function () {
        return this.each(function () {
            this.style.display = 'none';
        });
    };

    /**
     * class add&remove support
     * @returns {$.fn}
     */
    $.fn.addClass = function (clazz) {
        return this.each(function () {
            this.classList.add(clazz);
        });
    };
    $.fn.removeClass = function (clazz) {
        return this.each(function () {
            this.classList.remove(clazz);
        });
    };
    $.fn.hasClass = function (clazz) {
        var hasIt = true;
        this.each(function () {
            hasIt = this.classList.contains(clazz);
            return hasIt;
        });
        return hasIt;
    };

    /**
     * dom edit support
     * PS:the method will returned the current object ,the child will be returned in jQuery
     * @param elem
     * @returns {*|Element}
     */
    $.fn.append = function (elem) {
        return this.each(function(){
            if (typeof elem == 'string') {
                this.innerHTML += elem;
            }else{
                this.appendChild(elem);
            }
        });
    };


    /**
     * -------------------------------------------------------- some utils -----------------------------------------------------------------------------------
     */

        //isPercentage
    function $ip(val) {
        return /^\d+\.?\d+%$|^\d+%$/.test(val);
    }

    //isEmpty
    function $ie(o) {
        for (var i in o) {
            if (o[$hp](i)) return false;
        }
        return true;
    }

    //parseNumAndUnit
    function $pnu(val) {
        var a = /(^[+-.0-9]+)([a-zA-Z%]*)$/.exec(val);
        a == null && I.e("Can't parse value " + val);
        return {n: a[1], u: a.length > 2 ? a[2] : null};
    }

    //merge
    function $mg(dest, src) {
        for (var i in src) {
            if (src[$hp](i)) {
                var d = dest[i], s = src[i];
                !d && d !== false && (dest[i] = s);
            }
        }
        return dest;
    }

    /**
     * carousel support touch operation
     * -------------------------------------------------------- carousel begin -----------------------------------------------------------------------------------
     * default config
     */
    var csrDict = {'fast': 500, 'normal': 1000, 'slow': 1500, 'long': 5000, 'common': 3000, 'short': 1000};
    var defaultConfig = {
        moveSpeed: 'fast',
        stopTime: 'common',
        length: 0,
        p: 0
    };
    $.fn.carouselInit = function (opt) {
        return this.each(function () {
            var self = this, $i = $(this), items = [];
            opt = $mg(opt || {}, defaultConfig);
            $i.css({
                position: 'relative',
                overflow: 'hidden'
            }).touchStart(function (ev) {
                ev.preventDefault();
                var fs = this.crsConfig.frames, f = fs[0], $i = this;
                $i.$mvf = ev.targetTouches[0].pageX;
                $i.$mvt = $i.$mvf;
                f.ctx.u = function(r,ctx){
                    return $mv.call($i,ctx);
                };
                f.g.ctx.u = function(r,ctx){
                    var o = $mv.call($i,ctx);
                    $i.$mvf = $i.$mvt;
                    return o;
                };
                f.start();
            }).touchEnd(function (ev) {
                ev.preventDefault();
                var fs = this.crsConfig.frames, f = fs[0], $i = this, fx = f.ctx;
                $i.$mvt = null;
                $i.$mvf = null;
                fx.fp = f.g.ctx.fp = Math.ceil($pf($pnu(fx.fc.left || 0).n) / $pf($pnu(fx.fe.left).n) * fx.fn);
                fx.u = f.g.ctx.u = ufunc;
                $i.cfunc = null;
                f.pause();
            }).touchMove(function (ev) {
                ev.preventDefault();
                var t = ev.targetTouches[0],$i = this;
                $i.$mvt = t.pageX;
            });

            $i.find('.item').each(function () {
                var $i = $(this);
                opt.length += $i.width();
                $i.css({position: 'absolute', left: 0, top: 0, display: 'none'});
                items.push($i);
            });
            items[0].show();
            opt.items = items;
            self.crsConfig = opt;
            self.crsConfig.frames = items.length > 1 ? doCrsInit(self.crsConfig) : null;

            W.setInterval(function () {
                if (!self.$mvt) {
                    var fs = self.crsConfig.frames, f = fs.shift();
                    f.start();
                    fs.push(f);
                }
            }, csrDict[opt.stopTime] + csrDict[opt.moveSpeed]);
        });
    };

    function $mv(ctx) {
        ctx.fp = 1; //disable frame pointer
        var $i = this,tf = $i.$mvt - $i.$mvf;
        if(!tf) return ctx.fc;
        var l = ctx.fc.left || ctx.fs.left;

//        console.info(ctx.o[0].id);
//        console.info({left:($pf($pnu(l).n) + tf) + 'px'});

        return {left:($pf($pnu(l).n) + tf) + 'px'};
    }
    /**
     * add frames
     * ps : caption is useless for mobile，so it would not be support
     * @param cfg
     */
    function doCrsInit(cfg) {
        var it, nt, frames = [], sec = csrDict[cfg.moveSpeed];
        for (var i = 0; i < cfg.items.length; i++) {
            it = cfg.items[i];
            var iw = it.width();
            nt = (i + 1) == cfg.items.length ? cfg.items[0] : cfg.items[i + 1];
            I.i('[crs] it.width: ' + iw);
            frames.push(it.animate({
                from: { left: '0px'},
                to: { left: -iw + 'px'},
                sec: sec,
                callback: function () {
                    this.hide()
                }
            }).nextAnimate(nt, {
                from: { left: iw + 'px'},
                to: { left: '0px'},
                sec: sec
            }, true));
        }
        return frames;
    }

    /**
     * -------------------------------------------------------- carousel end -----------------------------------------------------------------------------------
     */

})();
